/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2023. All rights reserved.
 */

import { Json5Reader } from '@ohos/hvigor';
import { PackageJson } from 'type-fest';

import { findTargetProduct } from '../../common/find-target-product.js';
import { CommonConst } from '../../const/common-const.js';
import { ModuleModel } from '../../model/module/module-model.js';
import { ProjectModelImpl } from '../../model/project/project-model-impl.js';
import { TargetRuntimeOS } from '../../options/build/module-build-profile.js';
import { ProjectBuildProfile } from '../../options/build/project-build-profile.js';
import { ApiType } from '../../project/data/hap-extra-info.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ApiModel, OneSdkValidator } from '../../utils/one-sdk-validator.js';

import { InspectionI } from './inspection-i.js';
import ProductBuildOpt = ProjectBuildProfile.ProductBuildOpt;
import ProjectTargetBuildOpt = ProjectBuildProfile.ProjectTargetBuildOpt;

export class ModuleInspection implements InspectionI {
  private _log: OhosLogger = OhosLogger.getLogger('Inspection');
  private readonly projectModel: ProjectModelImpl;
  private readonly moduleModel: ModuleModel;

  constructor(moduleModel: ModuleModel) {
    this.moduleModel = moduleModel;
    this.projectModel = moduleModel.getParentProject() as ProjectModelImpl;
  }

  doInspection(): InspectionI {
    this.collectTargetRuntimeOS();
    this.checkModuleType();
    this.targetApiVersionCheck();
    this.targetBuildOptInspection();
    return this;
  }

  /**
   * 收集各个模块target的runtimeOS信息
   * 用于runtimeOS及API校验
   */
  collectTargetRuntimeOS() {
    const moduleOpt = this.projectModel.getModuleProfileOpt(this.moduleModel.getName());
    if (!moduleOpt || this.moduleModel.isHarModule()) {
      return;
    }

    this.moduleModel.getTargetOptions().forEach((moduleTarget) => {
      const optInProjectOpt: ProjectTargetBuildOpt | undefined = moduleOpt.targets?.find((item) => item.name === moduleTarget.name);
      if (moduleTarget.name === 'ohosTest') {
        return;
      }
      if (optInProjectOpt && optInProjectOpt.applyToProducts) {
        optInProjectOpt.applyToProducts.forEach((applyProduct) => {
          this.projectModel.getTargetRuntimeOSs().push({
            moduleName: this.moduleModel.getName(),
            targetName: moduleTarget.name,
            productName: applyProduct,
            runtimeOS: moduleTarget.runtimeOS,
          });
        });
      } else {
        this.projectModel.getTargetRuntimeOSs().push({
          moduleName: this.moduleModel.getName(),
          targetName: moduleTarget.name,
          productName: 'default',
          runtimeOS: moduleTarget.runtimeOS,
        });
      }
    });
  }

  targetApiVersionCheck(): void {
    const api: number = this.projectModel.getCompileApiMetaByProduct(findTargetProduct(this.projectModel).name).version;
    if (this.moduleModel.getApiType() === ApiType.STAGE && 9 > api) {
      this._log._buildError(`API version ${api} does not support the Stage model.`)._file(this.projectModel.getProfilePath())._record();
    }
  }

  targetBuildOptInspection() {
    const currentProduct: ProjectBuildProfile.ProductBuildOpt = findTargetProduct(this.projectModel);
    const runtimeOSs: TargetRuntimeOS[] = this.projectModel.getTargetRuntimeOSs();
    if (OneSdkValidator.evlApiModel(this.projectModel, currentProduct) !== ApiModel.ORIGINAL && !this.moduleModel.isHarModule()) {
      runtimeOSs
        .filter((item) => item.moduleName === this.moduleModel.getName() && item.productName === currentProduct.name)
        .forEach((item) => this.runtimeOSInspection(item, currentProduct));
    }
  }

  runtimeOSInspection(target: TargetRuntimeOS, product: ProductBuildOpt) {
    const runtimeOSs: TargetRuntimeOS[] = this.projectModel.getTargetRuntimeOSs();
    if (product.runtimeOS) {
      if (target.runtimeOS && target.runtimeOS !== product.runtimeOS) {
        this._log.printErrorExit(
          'RUNTIME_OS_DOES_NOT_MATCH',
          [this.moduleModel.getName(), target.targetName, product.name, this.moduleModel.getProfilePath()],
          [[target.runtimeOS, product.runtimeOS]]
        );
      }
    } else {
      const ohosTargets: TargetRuntimeOS[] = [];
      const hosTargets: TargetRuntimeOS[] = [];
      const ignoreTargets: TargetRuntimeOS[] = [];
      runtimeOSs
        .filter((item) => item.productName === product.name)
        .forEach((item) => {
          if (!item.runtimeOS) {
            ignoreTargets.push(item);
          } else if (item.runtimeOS === CommonConst.HARMONY_OS) {
            hosTargets.push(item);
          } else {
            ohosTargets.push(item);
          }
        });

      // product未配置runtimeOS, 如其他target皆为OpenHarmony或未配置, 则product runtimeOS为OpenHarmony
      if (hosTargets.length === 0) {
        return;
      }

      // 同个product中的target的runtimeOS同时存在HarmonyOS和OpenHarmony
      if (ohosTargets.length > 0 || ignoreTargets.length > 0) {
        ohosTargets.push(...ignoreTargets);
        const diffStr: string = ohosTargets.map<string>((item) => `${item.moduleName}:${item.targetName}`).join(',');
        const hosStr: string = hosTargets.map<string>((item) => `${item.moduleName}:${item.targetName}`).join(',');
        this._log.printErrorExit(
          'TARGETS_DO_NOT_MATCH',
          [diffStr, hosStr, this.moduleModel.getProfilePath()]
        );
      }
    }
  }

  private checkModuleType(): void {
    if (this.moduleModel.getApiType() === ApiType.FA || !this.moduleModel.isHarModule() || this.projectModel.isOhpmProject()) {
      return;
    }
    const pkgPath = this.moduleModel.getPackageJsonPath();
    const pkgFileName = CommonConst.PACKAGE_JSON;
    const packageJson: PackageJson = Json5Reader.getJson5Obj(pkgPath);
    const isESModule = packageJson.type === 'module';
    if (!isESModule) {
      this._log
        ._buildError(`Invalid module type! Please check the ${pkgFileName} of current library module.`)
        ._file(pkgPath)
        ._detail('Set \'{ "type": "module" }\'.')
        ._record();
    }
  }

  exitOnError(): void {
    this._log._printAllExit();
  }
}
